package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.Blog;
import com.hmdp.entity.Follow;
import com.hmdp.entity.ScrollResult;
import com.hmdp.entity.User;
import com.hmdp.mapper.BlogMapper;
import com.hmdp.mapper.FollowMapper;
import com.hmdp.service.IBlogService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.service.IUserService;
import com.hmdp.utils.SystemConstants;
import com.hmdp.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

import static com.hmdp.utils.RedisConstants.BLOG_LIKED_KEY;
import static com.hmdp.utils.RedisConstants.FEED_KEY;
@Slf4j
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
    @Resource
    private IBlogService blogService;
    @Resource
    private IUserService userService;

    @Resource
    private BlogMapper blogMapper;

    @Resource
    private FollowMapper followMapper;

    @Resource
    StringRedisTemplate stringRedisTemplate;

    /**
     * 查看具体blog
     * @param id
     * @return
     */
    @Override
    public Result queryById(String id) {
        Blog blog = getById(id);
        //获取blog相关的用户信息
        getUserThroughBlog(blog);
        //查看blog是否被用户点赞
        isBlogLiked(blog);

        return Result.ok(blog);

    }
    /**
     * 用户点赞此blog
     * @param id
     * @return
     */
    @Override
    public Result isLikeBlog(Long id) {
        //1.获取用户id
        Long userId = UserHolder.getUser().getId();
        //2.生成blog是否点赞的key
        String key = BLOG_LIKED_KEY + id;
        //3.查询redis的zset集合中用户是否点赞
        Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
        if(score != null){
            //4.1如果点赞了，数据库like点赞总数字段-1，并且删除set集合中的用户
            boolean isSuccess = update().setSql("liked = liked -1").eq("id", id).update();
            if(isSuccess){
                stringRedisTemplate.opsForZSet().remove(key,userId.toString());
            }
        }else {
            //4.2如果没有点赞，数据库like点赞总数字符+1,并且将用户添加进Zset集合中
            boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
            //zadd key value score
            if (isSuccess) {
                stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
            }
        }
        return Result.ok();
    }

    @Override
    public Result queryRecords(Integer current) {
        // 根据用户查询
        /*Page<Blog> page = blogService.query()
                .orderByDesc("liked")
                .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));*/
        Page<Blog>  page  = new Page<>(current,SystemConstants.MAX_PAGE_SIZE);
        LambdaQueryWrapper<Blog> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(Blog::getLiked);
        blogService.page(page, queryWrapper);
        // 获取当前页数据
        List<Blog> records = page.getRecords();

        // 查询用户
        records.forEach(blog -> {
            getUserThroughBlog(blog);
            isBlogLiked(blog);
        });
        return Result.ok(records);
    }


    /**
     * 获取点赞的用户集合返回给前端
     * @param id
     * @return
     */
    @Override
    public Result queryBlogLikes(Long id) {
        String key = BLOG_LIKED_KEY + id;
        //1.查询top5的点赞用户 zrange key 0 4
        Set<String> top5 = stringRedisTemplate.opsForZSet().range(key,0,4);
        if(top5 == null || top5.isEmpty()){
            return Result.ok(Collections.emptyList());
        }
        /*List<Long> ids = new ArrayList<>();
        //2.解析处其中的用户id
        for(String top: top5 ){
            ids.add(Long.parseLong(top));
        }*/
        List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
        //根据用户id查询用户 SELECT id,phone,PASSWORD,nick_name,icon,create_time,update_time FROM tb_user WHERE id IN ( 1010, 1005 ) ORDER BY FIELD(id,1010,1005);
        /*StringBuilder s = new StringBuilder();
        Iterator<Long> iterator = ids.iterator();
        while(iterator.hasNext()){
            Long next = iterator.next();
            if(iterator.hasNext()) {
                s.append(next).append(",");
            }else{
                s.append(next);
            }
        }*/
        String join = StrUtil.join(",", ids);
        /*List<User> users = userService.listByIds(ids);*/
         List<User> users = blogMapper.getListFiled(join);

        //将user类型转换成UserDTO类型
        List<UserDTO> userDTOs = users.stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());
        //返回
        return Result.ok(userDTOs);
    }

    @Override
    public Result saveBlog(Blog blog) {
       // 获取登录用户
        UserDTO user = UserHolder.getUser();
        blog.setUserId(user.getId());
        // 保存探店博文
        blogService.save(blog);

        //根据粉丝用户的个数实现推模式和拉模式
        List<Long> follows = followMapper.selectUserId(user.getId());
        int size = follows.size();
        //如果粉丝数量小于5000个，那么采用推模式
        if(size <= 5000){
            //------------------推模式--------------------------//
            //将该用户的文章推送给粉丝
            //实现方式:使用redis的zset集合进行缓存，用时间戳进行排序，key为粉丝的id
            //1.获取登录用户的粉丝 select user_id from tb_follow where follow_user_id = ?;
            for(Long fw : follows){
                String key = "feed:" + fw;
                stringRedisTemplate.opsForZSet().add(key,blog.getId().toString(),System.currentTimeMillis());
            }
        }else{
            //-------------------拉模式-------------------------//
            //1.V主发布blog的时候将消息放入redis中，用sortedset数据结构进行存储
            String key = "feedpull:" + user.getId();
            stringRedisTemplate.opsForZSet().add(key,blog.getId().toString(),System.currentTimeMillis());
            // 返回id
        }

        return Result.ok(blog.getId());

    }

    /**
     * Feed流推模式
     * @param lastId
     * @param offset
     * @return
     */
    @Override
    public Result queryBlogFollowersPush(Long lastId, Long offset) {
        Long userId = UserHolder.getUser().getId();
        String key =  FEED_KEY + userId;
        //1.分析：现在我们拥有的是lastId(即上一次的时间戳),offset(偏移量)
        //我们需要做的是：根据三个已知的条件，把blog的集合从redis中的查询出来
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, 0, lastId, offset, 2);

        //如果为空，直接返回
        if (typedTuples == null || typedTuples.isEmpty()) {
            return Result.ok();
        }
        //解析出set集合中的blogId和时间戳
        //将blogId放入List集合中
        List<Long> blogIdList = new ArrayList<>(typedTuples.size());
        //定义最小时间戳
        long minTime = 0L;
        //定义os来计算出返回给前端的偏移量
        Long os = 1L;
        for(ZSetOperations.TypedTuple<String> typedTuple: typedTuples){
            //getValue()方法是取出其中的blog;
            blogIdList.add(Long.parseLong(Objects.requireNonNull(typedTuple.getValue())));
            //getScore()方法是取中其中的score;
            long score = Objects.requireNonNull(typedTuple.getScore()).longValue();
            if(minTime == score) {
                os++;
          /*      minTime = score;*/
            } else{
                minTime = score;
                os = 1L;
            }
        }
        //根据blog的id去数据库中查询 select * from tb_blog where id in ( ?, ?, ?  ) ORDER BY FIELD(id,1010,1005)
        String ids = StrUtil.join(",",blogIdList);
     /*   System.out.println("+++++++++++++++++++++"+ids);*/
        /*List<Blog> blogs = query().in("id", blogIdList).last(" ORDER BY FIELD(id," + ids + ")").list();*/
        List<Blog> blogs = blogMapper.getBlogFiled(ids);

        //给每一个blog设置好特定的值
        blogs.forEach(blog -> {
            getUserThroughBlog(blog);
            isBlogLiked(blog);
        });

        ScrollResult result = new ScrollResult(blogs,minTime,os);
        return Result.ok(result);

    }

    /**
     * Feed拉模式
     * @param lastId
     * @param offset
     * @return
     */

    @Override
    public Result queryBlogFollowersPull(Long lastId, Long offset) {
        //先将粉丝关注V主发布的消息拉过来
        //1.查询redis中粉丝关注的用户id
        //登录用户的id
        Long userId = UserHolder.getUser().getId();
        //查询登录用户关注的V主
        Set<String> followersIds = stringRedisTemplate.opsForSet().members("follow:user" + userId);
        if(followersIds==null || followersIds.size()==0){
            return Result.ok();
        }
        List<Long> ids = followersIds.stream().map(Long::valueOf).collect(Collectors.toList());
        //2.将自己redis中存放的V主的消息中最大的时间戳拿出来
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores("feed:" + userId, 0, System.currentTimeMillis(), 0, 1);
        //如果用户redis存V主的消息一个都没有
        if(typedTuples == null || typedTuples.size()==0){
            for(Long followersId:ids){
                Set<ZSetOperations.TypedTuple<String>> typedTuplesFollowers = stringRedisTemplate.opsForZSet().rangeByScoreWithScores("feedpull:" + followersId, 0, System.currentTimeMillis());
                if(typedTuplesFollowers!=null && typedTuplesFollowers.size() != 0 ) {
                    for (ZSetOperations.TypedTuple<String> typedTuple : typedTuplesFollowers) {
                        stringRedisTemplate.opsForZSet().add("feed:" + userId, Objects.requireNonNull(typedTuple.getValue()),Objects.requireNonNull(typedTuple.getScore()));
                    }
                }
            }
        }
        //3.倒序查询每个关注的V主redis中存放的消息，用V主消息的时间戳跟最大时间戳对比，如果V主大就放入redis中，否则就停止放入
        else{
            ZSetOperations.TypedTuple<String> typedTupleOne = typedTuples.iterator().next();
            long maxTimeChuo = typedTupleOne.getScore().longValue();
            int count = 0;
            for(Long followersId:ids) {
                while(true){
                    Set<ZSetOperations.TypedTuple<String>> typedTuple = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores("feedpull:" + followersId, 0, System.currentTimeMillis(), count, 1);
                    if(typedTuple==null||typedTuple.isEmpty()){
                        count = 0;
                        break;

                    }
                    ZSetOperations.TypedTuple<String> next = typedTuple.iterator().next();
                    long timeChuo = next.getScore().longValue();
                    if(maxTimeChuo<timeChuo){
                        count++;
                        stringRedisTemplate.opsForZSet().add("feed:" + userId, Objects.requireNonNull(next.getValue()), Objects.requireNonNull(next.getScore()));
                    }else{
                        count=0;
                        break;
                    }
                }
            }
        }


        String key =  FEED_KEY + userId;
        //1.分析：现在我们拥有的是lastId(即上一次的时间戳),offset(偏移量)
        //我们需要做的是：根据三个已知的条件，把blog的集合从redis中的查询出来
        Set<ZSetOperations.TypedTuple<String>> typedTuples2 = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, 0, lastId, offset, 2);

        //如果为空，直接返回
        if (typedTuples2 == null || typedTuples2.isEmpty()) {
            return Result.ok();
        }
        //解析出set集合中的blogId和时间戳
        //将blogId放入List集合中
        List<Long> blogIdList = new ArrayList<>(typedTuples2.size());
        //定义最小时间戳
        long minTime = 0L;
        //定义os来计算出返回给前端的偏移量
        Long os = 1L;
        for(ZSetOperations.TypedTuple<String> typedTuple: typedTuples2){
            //getValue()方法是取出其中的blog
            blogIdList.add(Long.parseLong(Objects.requireNonNull(typedTuple.getValue())));
            //getScore()方法是取中其中的score;
            long score = Objects.requireNonNull(typedTuple.getScore()).longValue();
            if(minTime == score) {
                os++;
                /*      minTime = score;*/
            } else{
                minTime = score;
                os = 1L;
            }
        }
        //根据blog的id去数据库中查询 select * from tb_blog where id in ( ?, ?, ?  ) ORDER BY FIELD(id,1010,1005)
        String ids2 = StrUtil.join(",",blogIdList);
        /*   System.out.println("+++++++++++++++++++++"+ids);*/
        /*List<Blog> blogs = query().in("id", blogIdList).last(" ORDER BY FIELD(id," + ids + ")").list();*/
        List<Blog> blogs = blogMapper.getBlogFiled(ids2);

        //给每一个blog设置好特定的值
        blogs.forEach( blog ->{
            getUserThroughBlog(blog);
            isBlogLiked(blog);});

        ScrollResult result = new ScrollResult(blogs,minTime,os);
        return Result.ok(result);
    }


    //判断登录用户对此blog是否点赞，前端通过判断blog类的isLiked字段来显示高亮
    private void isBlogLiked(Blog blog){
        //1.获取用户id
        UserDTO user = UserHolder.getUser();
        if(user == null){
            return ;
        }
        Long userId = user.getId();
        //2.生成blog是否点赞的key
        String key = BLOG_LIKED_KEY + blog.getId();
        //3.查询redis的zset集合中用户是否点赞
        Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
        blog.setIsLike(score != null);
    }

    //获取blog中相关的用户信息
    private void getUserThroughBlog(Blog blog){
        Long userId = blog.getUserId();
        User user = userService.getById(userId);
        blog.setName(user.getNickName());
        blog.setIcon(user.getIcon());
    }
}
